package server

import (
	"crypto/tls"
	"github.com/akirakazu/go-simplejson"
	"github.com/gorilla/websocket"
	"github.com/nu7hatch/gouuid"
	"sync"
	"time"
)

type Proxy struct {
	id            string
	loginId       string
	binRecv       *json.Json
	connectStatus *sync.Mutex
	connected     bool
	ready         bool
	conn          *websocket.Conn
	server        *Server
	send          chan *WriteStruct
}

func newProxy(s *Server) *Proxy {
	proxy := new(Proxy)
	proxy.server = s
	proxy.connectStatus = &sync.Mutex{}
	proxy.connected = false
	proxy.ready = false
	proxy.send = make(chan *WriteStruct, 1000)
	go proxy.connect()
	return proxy
}

func (p *Proxy) sendPack(m *Pack) bool {
	if !p.connected {
		return false
	}
	if m.Cmd != "login" && !p.ready {
		return false
	}
	addr := p.conn.RemoteAddr().String()
	p.server.logPack("send", m, addr)
	json, err := m.MarshalJSON()
	if err != nil {
		return false
	}
	w := &WriteStruct{T: websocket.TextMessage, Data: json}
	p.send <- w
	return true
}

func (p *Proxy) sendBinary(b []byte) bool {
	if !p.ready {
		return false
	}

	addr := p.conn.RemoteAddr().String()
	p.server.logBinary("send", b, addr)

	w := &WriteStruct{T: websocket.BinaryMessage, Data: b}
	p.send <- w
	return true
}

func (p *Proxy) readPump() {
	for {
		if !p.connected || p.conn == nil {
			continue
		}

		t, data, err := p.conn.ReadMessage()
		if err != nil {
			p.retry()
			return
		}

		if t == websocket.TextMessage {
			m, err := Validate(string(data))
			if err != nil {
				continue
			}
			p.onTextMessage(m)
		} else if t == websocket.BinaryMessage {
			p.onBinMessage(data)
		}
	}
}

func (p *Proxy) writePump() {
	for {
		if !p.connected || p.conn == nil {
			continue
		}
		message := <-p.send
		err := p.conn.WriteMessage(message.T, message.Data)
		if err != nil {
			p.retry()
			return
		}
	}
}

func (p *Proxy) onTextMessage(m *Pack) {
	addr := p.conn.RemoteAddr().String()
	p.server.logPack("recv", m, addr)
	if m.Id == p.loginId {
		p.onLoginResp()
		p.loginId = ""
	} else {
		cmd := m.Cmd
		if cmd == "request" ||
			cmd == "response" ||
			cmd == "start_binary" ||
			cmd == "end_binary" {
			p.server.proxyPack("", m)
			return
		}

		if cmd == "binary" {
			p.server.onBinary("", m)
		} else if cmd == "push" {
			p.server.onPush("", m)
		} else if cmd == "publish" {
			p.server.onPublish("", m)
		}
	}
}

func (p *Proxy) onBinMessage(data []byte) {
	if p.binRecv == nil {
		return
	}

	binRecvString := JSONToString(p.binRecv)
	receiverid, found := p.server.findConnWithoutSelfId(p.binRecv)
	if found {
		p.server.pb.sendBinary(receiverid, data)
	}
	if connId, ok := p.server.caches[binRecvString]; ok {
		p.server.pb.sendBinary(connId, data)
	}
	delete(p.server.caches, binRecvString)
	p.binRecv = nil
}

func (p *Proxy) connect() {
	p.connectStatus.Lock()
	if p.connected {
		p.connectStatus.Unlock()
		return
	}
	log.Println("connecting...")
	url := "wss://" + PROXY_URI + ":443/"
	c := &websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	conn, _, err := c.Dial(url, nil)

	if err != nil {
		log.Frint(err)
		p.connectStatus.Unlock()
		p.retry()
		return
	}

	conn.SetReadDeadline(time.Now().Add(150 * time.Second))
	conn.SetWriteDeadline(time.Now().Add(150 * time.Second))

	// SetPingHandler
	conn.SetPingHandler(func(data string) error {
		conn.WriteControl(websocket.PongMessage, []byte(""), time.Now().Add(time.Second*10))
		conn.SetReadDeadline(time.Now().Add(150 * time.Second))
		conn.SetWriteDeadline(time.Now().Add(150 * time.Second))
		return nil
	})
	p.connected = true
	p.conn = conn
	go p.readPump()
	go p.writePump()
	p.connectStatus.Unlock()
	p.login()
}

func (p *Proxy) retry() {
	p.connected = false
	p.ready = false
	if p.conn != nil {
		p.conn.Close()
	}
	p.conn = nil
	time.Sleep(5 * time.Second)
	p.connect()
}

func (p *Proxy) login() {
	msg := json.New()
	identity := json.New()
	identity.Set("client_type", "__proxy")

	if p.server.ccuId == "" {
		uid, _ := uuid.NewV4()
		id := uid.String()
		p.id = id
	} else {
		p.id = p.server.ccuId
	}
	identity.Set("session_id", p.id)
	msg.Set("identity", identity)
	msg.Set("pwd", PROXY_PWD)
	pack := NewPack("login", msg)
	p.loginId = pack.Id
	p.sendPack(pack)
}

func (p *Proxy) onLoginResp() {
	log.Debug("login ok")
	p.ready = true
}
